"""
Introduction
============

Pattern matching allows you to create a function that can react differently
according to the arguments used in a function.

This is a pattern matching example::

    >>> @pattern_matching
    >>> def foo(a, b, c):
    ...     '''foo'''
    ...
    >>> @foo.dispatch(1,_,_)
    >>> def bar(a, b, c):
    ...     assert a == 1
    ...     return 1
    ...
    >>> @foo.dispatch(_, lst[1:_], _)
    >>> def bar(a,b,c):
    ...     assert b[0] == 1
    ...     return 2
    ...
    >>> foo(1,2,3)
    1
    >>> foo(3,[1,2,3],3)
    2

Advanced Use
===========

This pattern matching decorator works with a multiple dispatcher that branches
according to functions testing each argument. This means that you can define
your own functions and used them to make it match or not.

For example, the '_' operator is a simple lambda function that accepts an
argument and returns True.

You could create a matcher that matches an argument if it's an instance of a
class like this::

    def on(a_type):
        return lambda x: isinstance(x, a_type)

    @pattern_matching
    def foo(a):
        '''does something cool'''

    @foo.dispatch(on(str))
    def bar(a):
        print repr(a), "is a string"
        
"""
__author__ = "Tiago Cogumbreiro <cogumbreiro@users.sf.net>"
__license__ = "MIT license <http://www.opensource.org/licenses/mit-license.php>"

class NotFoundError(StandardError):
    pass

class Lst(object):
    """Helper for lists"""
    def __getitem__(self, slice):
        return self(slice.start, slice.stop)

    def __call__(self, head, tail):
        head = wrap_value(head)
        tail = wrap_value(tail)
        def checker(a_list):
            if len(a_list) < 2:
                return False
            return head(a_list[0]) and tail(a_list[1:])
        return checker
        
def wrap_value(val):
    """if it's a callable returns it if not returns
    a function that will check if the received paramater
    equals this 'val'."""
    
    if callable(val):
        return val
        
    return lambda x: x == val
        
class Matcher(object):
    def __init__(self, master, *args):
        self.master = master
        self.checkers = [wrap_value(arg) for arg in args]
        
    def __call__(self, func):
        self.func = func
        # we don't want these functions to be called so we return None
        # and it won't be callable
        return None

    def check_args(self, args):
        matchers = iter(self.checkers)
        for arg in args:
            matches = matchers.next()
            if not matches(arg):
                raise NotFoundError
        
class PatternMatching(object):
    def __init__(self, func):
        self.matchers = []
        
    def dispatch(self, *args, **kwargs):
        """This method is called when we define a pattern
        and submit the function that should be called when it is accepted."""
        m = Matcher(self, *args, **kwargs)
        self.matchers.append(m)
        return m

    def run(self, *args, **kwargs):
        for m in self.matchers:
            try:
                m.check_args(args)
                return m.func(*args, **kwargs)
            except NotFoundError:
                pass
        raise NotFoundError("No matches for this function")
    
    __call__ = run
    
pattern_matching = PatternMatching

_ = lambda x: True

lst = Lst()


